﻿using System;
using System.Collections.Generic;
using Cubezicles.NeedObjects;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Cubezicles
{
    public struct MoodLevels
    {
        // Clamps to prevent from going negative or exceeding 100%
        private float _hunger, _bladder, _focus, _fun, _energy;
        public float hunger
        {
            get { return _hunger; }
            set { _hunger = MathHelper.Clamp(value, -1f, 1f); }
        }

        public float bladder
        {
            get { return _bladder; }
            set { _bladder = MathHelper.Clamp(value, -1f, 1f); }
        }

        public float focus
        {
            get { return _focus; }
            set { _focus = MathHelper.Clamp(value, -1f, 1f); }
        }

        public float fun
        {
            get { return _fun; }
            set { _fun = MathHelper.Clamp(value, -1f, 1f); }
        }

        public float energy
        {
            get { return _energy; }
            set { _energy = MathHelper.Clamp(value, -1f, 1f); }
        }

        //
        // TODO: make some sort of function that allows more readable rates?
        // TODO: make constants for decay rate values
        //

        public static MoodLevels DefaultDecayRates
        {
            get
            {
                return new MoodLevels()
                {
                    hunger = 0.00028f / 250,
                    //good-ish
                    bladder = 0.0003f / 250, // this was too high! always had to pee....0.00033f / 250,
                    focus = 0.0002f / 250,
                    // think this is a good rate! Yeah, their focus is gooooone by 3, lol
                    fun = 0.00024f / 250,
                    energy = 0.00015f / 250
                };
            }
            /*set
            {
                new MoodLevels()
                    {
                        hunger = 0.00028f / 250,
                        bladder = 0.0003f / 250,
                        fun = 0.00024f / 250,
                        energy = 0.00015f / 250,
                        focus = SetDefaultFocusDecay //.....................need to figure out how to use attention span to set focus decay rate
                    };
            }*/
        }

        // number of clock ticks in 

        #region StateBasedDecayRates

        // In comments, "increase" refers to getting closer to 100%, decrease refers to getting closer to 0%
        // (can be confusing when talking about increasing bladder by going to bathroom)
        // Also, negative values for increasing needs because they are DECAY rates and are subtracted

        // todo: create constants or enums for decay rate levels....
        // also todo: these will all become decay rates associated with objects....

        public static MoodLevels IdleDecayRates
        {
            get
            {
                return new MoodLevels()
                {
                    hunger = 0f / 250,
                    bladder = 0f / 250,
                    focus = 0f / 250,
                    fun = 0f / 250,
                    energy = 0f / 250
                };
            }
        }

        public static MoodLevels AtHomeDecayRates
        {
            get
            {
                return new MoodLevels()
                {
                    hunger = -0.0015f / 250,  // todo: maybe change these??
                    bladder = -0.0015f / 250,
                    focus = -0.0015f / 250,
                    fun = -0.0015f / 250,
                    energy = -0.0015f / 250
                };
            }
        }

        public static MoodLevels EatingDecayRates
        {
            get
            {
                return new MoodLevels()
                {
                    hunger = -0.002f / 250,     // hunger:  strong increase
                    bladder = 0.0004f / 250,    // bladder: slight decrease
                    focus = -0.0002f / 250,     // focus:   slight increase
                    fun = 0.0f / 250,           // fun:     no change
                    energy = -0.0002f / 250     // energy:  slight increase
                };
            }
        }

        public static MoodLevels BathroomingDecayRates
        {
            get
            {
                return new MoodLevels()
                {
                    hunger = 0.0f / 250,        // hunger:  no change
                    bladder = -0.01f / 250,     // bladder: strong increase
                    focus = -0.00015f / 250,    // focus:   slight increase
                    fun = 0.0f / 250,           // fun:     no change
                    energy = 0.0f / 250         // energy:  no change
                };
            }
        }

        public static MoodLevels HavingFunDecayRates
        {
            get
            {
                return new MoodLevels()
                {
                    hunger = 0.0002f / 250,    // slightly less than normal hunger decrease
                    bladder = 0.00033f / 250,   // normal bladder decrease
                    focus = 0.0002f / 250,      // todo
                    fun = -0.002f / 250,       // todo....
                    energy = 0.00015f / 250     // todo
                };
            }
        }

        public static MoodLevels EnergizingDecayRates
        {
            get
            {
                return new MoodLevels()
                {
                    hunger = 0.00018f / 250,    // hunger:  slight decrease
                    bladder = 0.0004f / 250,    // bladder: moderate decrease
                    focus = -0.0002f / 250,     // focus:   moderate increase
                    fun = -0.0001f / 250,       // fun:     slight increase
                    energy = -0.0008f / 250     // energy:  increase
                };
            }
        }

        public static MoodLevels MovingDecayRates
        {
            get { return MoodLevels.IdleDecayRates; }
        }

        #endregion
    }


    public class NeedsManager
    {
        private Boolean writeStatsOn = false;
        public void ToggleWriteStats()
        {
            writeStatsOn = !writeStatsOn;
        }

        public void EnableWriteStats() { writeStatsOn = true; }
        public void DisableWriteStats() { writeStatsOn = false; }

        public float SetDefaultFocusDecay(int attnSpan)
        {
            // float focusDecayRate = 
            return (8-attnSpan)/250000f;
        }

        //-- Employee "needs" - measured on a meter of 0 to 1 --//
        internal MoodLevels moodLevels;
        //public MoodLevels MoodLevels { get { return moodLevels; } }

        // Rate of decay for those needs
        MoodLevels decayRates = MoodLevels.DefaultDecayRates;

        // todo: states might be changed to be based on object they are using?! in addition to working, idle, athome
        // The current state of the Employee (what they are currently doing)
        // Used to determine which ...
        // change TakingABreak to relate to focus? ***... don't think we'll even use it??? should improve focus...
        //  public enum State { Idle = 0, AtHome, Working, TakingABreak, Eating, Bathrooming, HavingFun, Energizing, Moving } 

        public enum State { Idle = 0, AtHome, Working, TakingABreak, Eating, Bathrooming, HavingFun, Energizing, Moving }

        // Current Employee state
        internal State currentState; // = State.Idle;
        internal State nextState; // = State.Idle;
        public bool moved = false; 
        
        private Employee emp;

        // Get the Game1 object that this object belongs to
        private Game1 game;

        // the limit that employees will change their state and
        // try to fulfill a need if it drops below this level
        private const float levelLimit = .3f; //low

        // also have a "critical" limit....
        private const float criticalLimit = .05f; // critical

        private const float distress = .15f; // distress mood / despair


        public NeedsManager(Employee employee, Game1 game1)
        {
            emp = employee;
            game = game1;

            currentState = State.Idle;
            nextState = State.Idle;
        }

        
        /// <summary>
        /// Updates the state of the employee based on their current need levels.
        /// </summary>
        /// <returns>should return whether or not there was a change in employee's state...not sure if it works...</returns>
        public void UpdateEmployeeState()
        {
            int tempCritNeedCount = 0;
            
            // If ANY need reaches critical level, the employee will go home for the rest of the day (loss of revenue)
            // ...this isn't a good working model, a particular need will hit bottom before employee ever hits despair

            if (moodLevels.hunger <= criticalLimit)
                tempCritNeedCount++;
            if (moodLevels.fun <= criticalLimit)
                tempCritNeedCount++;
            if (moodLevels.bladder <= criticalLimit)
                tempCritNeedCount++;
            if (moodLevels.energy <= criticalLimit)
                tempCritNeedCount++;
            if (moodLevels.focus <= criticalLimit)
                tempCritNeedCount++;

            // If 2 needs reach critical, then go home??
            if (tempCritNeedCount >= 2) emp.GoHome(); // todo: make user notification bar

            // If overall mood gets to distress level, then go home
            if (emp.OverallMood <= distress) emp.GoHome(); // todo: make user notification bar, notify when go home


            // --- Check if employee reached their destination, update state if they did --- //
            if (emp.ReachedDestination() && !moved)
            {
                currentState = nextState;
                nextState = State.Working;
                moved = true;
            }
            if (currentState == State.Moving)
            {
                moved = false;
            }

            ItemManager im = game.ItemManager;

            NeedObject vend = im.GetItem<VendingMachine>();
            NeedObject drink = im.GetItem<DrinkMachine>();
            NeedObject pinball = im.GetItem<PinballMachine>();
            NeedObject bathroom = im.GetItem<Bathroom>();

            #region checkNeedFulfillment

            switch (emp.CurrentState)
            {
                // if they are at home, can't update any values - they stay at home til next day
                case State.AtHome:
                    return;
                //case State.TakingABreak:
                //case State.Idle:

                case State.Eating:
                    // check if hunger need is fully satisfied
                    checkFulfillment(moodLevels.hunger, vend);
                    break;

                case State.HavingFun:
                    checkFulfillment(moodLevels.fun, pinball);
                    break;

                case State.Bathrooming:
                    checkFulfillment(moodLevels.bladder, bathroom);
                    break;

                case State.Energizing:
                    checkFulfillment(moodLevels.energy, drink);
                    break;

                case State.Working:
                    if (!emp.isMoving || emp.changeInState) emp.setDestination(emp.HomeNode);
                    break;
            }

            #endregion

            emp.changeInState = false; //.......?

            // originally these were only in working, but these should be checked
            // no matter what the current state of employee is

            //--- overview of following code idea (not yet implemented fully) ----//

            // Check if the employee's need is low
            // if it is, check if an item to fulfill this need exists in the workplace (a NeedsObject)
            // then, check if the item is already in use
            // if not, stop working and go fulfill your need
            //         until your need is fulfilled (or something else happens, like instructed to go back to work)
            // otherwise.... keep working, until a need (any need) hits rock bottom, then you go home for the day...??

            #region checkNeedsLow

            // *****should these be ifs, or else-ifs....? what need is most important for them to satisfy??

            #region HungerLow
            // the employee's hunger need is low
            if (moodLevels.hunger <= levelLimit)
            {
                // check if an item to fulfill their hunger need exists (vending machine)
                // and if it is exists, check if it is in use
                if (vend != null && vend.CurrentUser == null)
                {
                    // if not, stop working, and go use the object
                    vend.SetUser(emp); //SetNextUser(emp); // vend.SetUser(emp)
                    emp.setDestination(im.nvend);
                    
                    currentState = State.Moving;
                    nextState = State.Eating;
                }
                // else,
                // they're hungry but either the object is in use or does not exist
                // so they keep working...
            }
            #endregion

            #region EnergyLow
            if (moodLevels.energy <= levelLimit)
            {
                // check if an item to fulfill their energy need exists (drink machine)
                // and if it is exists, check if it is in use
                if (drink != null && drink.CurrentUser == null)
                {
                    // if not, stop working, and go use the object
                    drink.SetUser(emp); //SetNextUser(emp);//drink.SetUser(emp);
                    emp.setDestination(im.ndrink);
                    //if (!isMoving || changeInState) setDestination(im.ndrink);

                    currentState = State.Moving;
                    nextState = State.Energizing;
                    //changeInState = true;
                }
                // else,
                // they're sleepy but either the object is in use or does not exist
                // so they keep working...
            }
            #endregion

            #region BladderLow
            if (moodLevels.bladder <= levelLimit)
            {
                // check if an item to fulfill their bladder need exists (bathroom machine)
                // and if it is exists, check if it is in use
                if (bathroom != null && bathroom.CurrentUser == null)
                {
                    // if not, stop working, and go use the object
                    bathroom.SetUser(emp); //SetNextUser(emp); //SetUser(emp);
                    emp.setDestination(im.nbath);
                    
                    currentState = State.Moving;
                    nextState = State.Bathrooming;
                    
                }
                // else,
                // they have to pee but either the bathroom is in use or does not exist
                // so they keep working...
            }
            #endregion

            #region FunLow
            if (moodLevels.fun <= levelLimit)
            {
                // check if an item to fulfill their fun need exists (pinball machine)
                // and if it is exists, check if it is in use
                if (pinball != null && pinball.CurrentUser == null)
                {
                    // if not, stop working, and go use the object
                    pinball.SetUser(emp); //SetNextUser(emp); //SetUser(emp);
                    emp.setDestination(im.npin);
                    
                    currentState = State.Moving;
                    nextState = State.HavingFun;
                    
                }
                // else,
                // they're bored but either the object is in use or does not exist
                // so they keep working...
            }
            #endregion

            // note: no check for focus, no object specifically for focus

            #endregion
        }

        private void checkFulfillment(float moodLevel, NeedObject needObj)
        {
            if (moodLevel >= 1)
            {
                // if it is, they stop using the need object
                if (needObj != null)
                {
                    needObj.ClearUser();
                }
                //... and go back to work
                emp.GetBackToWork();

                // todo: this makes it too easy! workers should be reluctant to go back to work....***

                //currentState = State.Working;
                emp.changeInState = true;
            }
        }


        public void WriteStats(SpriteBatch spr)
        {
            if (!writeStatsOn) return;
            float currY = 200;
            string[] ratenames = { "state", "decayRates.bladder", "decayRates.energy", "decayRates.focus", "decayRates.fun", "decayRates.hunger" };
            float[] rates = { (float)currentState, (float)decayRates.bladder, decayRates.energy, decayRates.focus, decayRates.fun, decayRates.hunger };
            for (int ii = 0; ii < rates.Length; ii++)
            {
                spr.DrawString(game.Fonts["status"], ratenames[ii] + ": " + rates[ii], new Vector2(300, currY), Color.YellowGreen);
                currY += game.Fonts["status"].MeasureString(ratenames[ii]).Y;
            }
        }

        /// <summary>
        /// Update this employee's need decay rates based on their current State
        /// </summary>
        public void UpdateDecayRates()
        {
            switch (currentState)
            {
                case State.Idle:
                    decayRates = MoodLevels.DefaultDecayRates;
                    break;
                case State.AtHome:
                    decayRates = MoodLevels.AtHomeDecayRates;
                    break;
                case State.Working:
                    decayRates = MoodLevels.DefaultDecayRates;
                    break;
                case State.Eating:
                    decayRates = MoodLevels.EatingDecayRates;
                    break;
                case State.HavingFun:
                    decayRates = MoodLevels.HavingFunDecayRates;
                    break;
                case State.Bathrooming:
                    decayRates = MoodLevels.BathroomingDecayRates;
                    break;
                case State.Energizing:
                    decayRates = MoodLevels.EnergizingDecayRates;
                    break;
                case State.Moving: // 
                    decayRates = MoodLevels.IdleDecayRates;
                    break;
                default:
                    decayRates = MoodLevels.DefaultDecayRates;
                    break;
            }
        }

        /// <summary>
        /// Update the current need levels of the employee with the current decay rates
        /// </summary>
        public void UpdateNeedLevels()
        {

            // Prevent from going negative or exceeding 100%
            if (moodLevels.hunger > 0 && moodLevels.hunger <= 1)
                moodLevels.hunger -= (float)game.time.CurrTimeSpeed * decayRates.hunger;
            if (moodLevels.fun > 0 && moodLevels.fun <= 1)
                moodLevels.fun -= (float)game.time.CurrTimeSpeed * decayRates.fun;
            if (moodLevels.energy > 0 && moodLevels.energy <= 1)
                moodLevels.energy -= (float)game.time.CurrTimeSpeed * decayRates.energy;
            if (moodLevels.bladder > 0 && moodLevels.bladder <= 1)
                moodLevels.bladder -= (float)game.time.CurrTimeSpeed * decayRates.bladder;
            if (moodLevels.focus > 0 && moodLevels.focus <= 1)
                moodLevels.focus -= (float)game.time.CurrTimeSpeed * decayRates.focus;

            /*
            moodLevels.hunger -= (float)game.time.CurrTimeSpeed * decayRates.hunger;
            
            moodLevels.fun -= (float)game.time.CurrTimeSpeed * decayRates.fun;
            
            moodLevels.energy -= (float)game.time.CurrTimeSpeed * decayRates.energy;
            
            moodLevels.bladder -= (float)game.time.CurrTimeSpeed * decayRates.bladder;
            
            moodLevels.focus -= (float)game.time.CurrTimeSpeed * decayRates.focus;
             * */

            // from revision 117! so long ago!
            //another check....testing to see if this is the problem for needs not decaying after being filed once
            // yay it works now... sometimes decay rates push it over the limit
            if (moodLevels.hunger > 1) moodLevels.hunger = 1;
            if (moodLevels.fun > 1) moodLevels.fun = 1;
            if (moodLevels.energy > 1) moodLevels.energy = 1;
            if (moodLevels.bladder > 1) moodLevels.bladder = 1;
            if (moodLevels.focus > 1) moodLevels.focus = 1;

            if (moodLevels.hunger < 0) moodLevels.hunger = 0;
            if (moodLevels.fun < 0) moodLevels.fun = 0;
            if (moodLevels.energy < 0) moodLevels.energy = 0;
            if (moodLevels.bladder < 0) moodLevels.bladder = 0;
            if (moodLevels.focus < 0) moodLevels.focus = 0;
        }

        /// <summary>
        /// Convert the current state to a string
        /// </summary>
        /// <returns>the employee's current state in printable string form</returns>
        public string GetStateString()
        {
            switch (currentState)
            {
                case State.Idle:
                    return "Idle";
                case State.AtHome:
                    return "At Home";
                case State.Working:
                    return "Working";
                case State.TakingABreak:
                    return "Taking a Break";
                case State.Eating:
                    return "Eating";
                case State.HavingFun:
                    return "Having Fun";
                case State.Bathrooming:
                    return "Bathroom-ing";
                case State.Energizing:
                    return "Energizing";
                case State.Moving:
                    return "Moving...";
                default:
                    return "Unknown";
            }
        }

        public void GoToBathroom(NeedObject bathroom, Node destination)
       {
           // if not, stop working, and go use the object
           bathroom.SetUser(emp); //SetNextUser(emp);
           emp.setDestination(destination); 
           currentState = State.Moving;
           nextState = State.Bathrooming;
       }

       /*public void GoToPinballMachine(NeedObject pinball, Node destination)
       {
            
       }*/

       public bool GetBackToWork()
       {
           // todo: theres probably  more to finishing get back to work...
           // if they have gone home for the day, they refuse to get back to work
           if (currentState == State.AtHome)
               return false;

           emp.setDestination(emp.HomeNode);
           nextState = State.Working;
           currentState = State.Moving;

           emp.changeInState = true;

           return true;
       }

       /// <summary>
       /// 
       /// </summary>
       public void GoHome()
       {
           currentState = State.AtHome;
           emp.setDestination(game.EmployeeManager.ndoor); // was game1
           // todo: theres probably  more to finishing go home.... shouldnt draw any more for one
       }


        // notifications....? not til final at least
    }
}
